//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Threading;
using Microsoft.Research.ScientificWorkflow.TridentModelSL;
using Microsoft.Research.ScientificWorkflow.WebClient.Common;
using Microsoft.Research.ScientificWorkflow.WorkflowApplicationCommonSL;
using System.Globalization;

namespace Microsoft.Research.ScientificWorkflow.WebClient.Views.WorkflowApplication
{
    /// <summary>
    /// Represents work area in the trident work flow application
    /// </summary>
    public partial class WorkAreaView : UserControl, IView, INotifyPropertyChanged
    {
        #region Private fields
        private static bool workflowOpen;
        private static bool executionCompleted;
        private static bool jobUnderExecution;
        private TridentApplication application;
        private static Guid currentWorkflowId;
        private static JobDetails currentJob;
        private static DispatcherTimer jobMonitorTimer;

        private static int intervalElapsed;

        const int ConfiguredInterval = 90;
        private static TextBlock txtBlkWfName;

        private static TextBox txtBxJbName;

        private static TabControl tabCntrPaneCtrl;

        private static Grid detlsGrid;

        private static Grid resltsGrid;

        private static ComboBox nodesCombo;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="WorkAreaView"/> class.
        /// </summary>
        public WorkAreaView()
        {
            InitializeComponent();
            this.AttachCommands();
            WorkAreaView.DisplayNodes = new ObservableCollection<NodeModel>();
            this.DisplayUsers = new ObservableCollection<UserModel>();
            WorkAreaView.WorkflowRequiredParameters = new ObservableCollection<ParameterDescriptionModel>();
            WorkflowOutputs = new ObservableCollection<WorkflowOutputModel>();
            ExecutionInputs = new ObservableCollection<ParameterDescriptionModel>();
            DataProducts = new ObservableCollection<WorkflowOutputActivityModel>();

            this.cboNode.ItemsSource = WorkAreaView.DisplayNodes;
            this.dataGridInputParameters.ItemsSource = WorkAreaView.WorkflowRequiredParameters;
            this.WorkflowOuputsList.ItemsSource = WorkflowOutputs;
            this.dgExecutionWorkflowInputs.ItemsSource = ExecutionInputs;
            this.dataproductsTree.ItemsSource = DataProducts;

            this.InitializeTimer();

            WorkAreaView.txtBlkWfName = this.tbkWorkflowName;
            WorkAreaView.txtBxJbName = this.tbxJobName;
            WorkAreaView.tabCntrPaneCtrl = this.tabCenterpane;
            WorkAreaView.detlsGrid = this.detailsGrid;
            WorkAreaView.resltsGrid = this.resultsGrid;
            WorkAreaView.nodesCombo = this.cboNode;
        }

        static WorkAreaView()
        {
            RegistryService.GetWorkflowByIdCompleted += new EventHandler<GetWorkflowByIdCompletedEventArgs>(OnGetWorkflowByIdCompleted);
            RegistryService.ExecuteWFOnMachineCompleted += new EventHandler<ExecuteWFOnMachineCompletedEventArgs>(OnExecuteWorkflowCompleted);
            RegistryService.TerminateJobCompleted += new EventHandler<CompletedEventArgs>(OnTerminateJobCompleted);
            RegistryService.GetOutputsCompleted += new EventHandler<GetOutputsCompletedEventArgs>(OnGetOutputsCompleted);
            RegistryService.GetJobDetailsCompleted += new EventHandler<GetJobDetailsCompletedEventArgs>(OnGetJobDetailsCompleted);
        }
        #endregion

        #region IView Members

        /// <summary>
        /// Fired for launching of the wait screen.
        /// </summary>
        public event EventHandler LaunchWaitScreen;

        /// <summary>
        /// Fired for removing of the wait screen.
        /// </summary>
        public event EventHandler RemoveWaitScreen;

        /// <summary>
        /// Fired for launching of the wait screen.
        /// </summary>
        private static event EventHandler LaunchWaitScreenStatic;

        /// <summary>
        /// Fired for removing of the wait screen.
        /// </summary>
        private static event EventHandler RemoveWaitScreenStatic;

        /// <summary>
        /// Gets the view.
        /// </summary>
        /// <value>The view.</value>
        public FrameworkElement View
        {
            get
            {
                return this;
            }
        }

        /// <summary>
        /// Initializes this view.
        /// </summary>
        public void Initialize()
        {
            this.application = TridentApplication.Instance;
            currentWorkflowId = Guid.Empty;
            WorkAreaView.LaunchWaitScreenStatic = this.LaunchWaitScreen;
            WorkAreaView.RemoveWaitScreenStatic = this.RemoveWaitScreen;
        }
        #endregion

        #region Properties

        /// <summary>
        /// 
        /// </summary>
        public static ObservableCollection<NodeModel> DisplayNodes { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        public ObservableCollection<UserModel> DisplayUsers { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        public static ObservableCollection<ParameterDescriptionModel> WorkflowRequiredParameters { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        public static ObservableCollection<WorkflowOutputModel> WorkflowOutputs { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        public static ObservableCollection<ParameterDescriptionModel> ExecutionInputs { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        public static ObservableCollection<WorkflowOutputActivityModel> DataProducts { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        private static bool ExecutionInProgress
        {
            get
            {
                return WorkAreaView.jobUnderExecution;
            }
            set
            {
                WorkAreaView.jobUnderExecution = value;
                HtmlPage.Window.SetProperty("needToConfirm", value);
                WorkAreaView.RaiseCanExecuteChanged();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private static bool WorkflowOpen
        {
            get
            {
                return WorkAreaView.workflowOpen;
            }
            set
            {
                WorkAreaView.workflowOpen = value;
                WorkAreaView.RaiseCanExecuteChanged();
            }
        }

        #endregion

        #region Command Execute Methods
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        private void ExecuteWorkflow(object sender)
        {
            string errorMessage = ValidateInputs();
            try
            {
                if (string.IsNullOrEmpty(errorMessage))
                {
                    JobDescriptionModel job = new JobDescriptionModel()
                    {
                        JobName = this.tbxJobName.Text.Trim(),
                        NodeToExecuteOn = this.cboNode.SelectedItem as NodeModel,
                        WorkflowId = currentWorkflowId,
                        RequiredParameters = WorkflowRequiredParameters.ToList(),
                        JobNotes = this.tbxNotes.Text.Trim()
                    };

                    WorkAreaView.LaunchWait();
                    RegistryService.ExecuteWFOnMachineAsync(job, TridentApplication.Instance.User);
                    this.tabCenterpane.SelectedIndex = 1;
                }
                else
                {
                    HtmlPage.Window.Alert(errorMessage);
                }
            }
            catch (TridentCustomException ex)
            {
                WorkAreaView.RemoveWait();
                HtmlPage.Window.Alert(ex.Message);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        private void WorkflowSelected(object sender)
        {
            try
            {
                if (sender != null)
                {
                    WorkAreaView.LaunchWait();
                    WorkflowModel model = sender as WorkflowModel;
                    RegistryService.GetWorkflowByIdAsync(model.Id);
                }
            }
            catch (TridentCustomException ex)
            {
                WorkAreaView.RemoveWait();
                HtmlPage.Window.Alert(ex.Message);
            }
        }

        /// <summary>
        /// Called when get workflow by id completed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Research.ScientificWorkflow.GetWorkflowByIdCompletedEventArgs"/> instance containing the event data.</param>
        private static void OnGetWorkflowByIdCompleted(object sender, GetWorkflowByIdCompletedEventArgs e)
        {
            try
            {
                if (null == e.Error && e.WorkflowModel != null)
                {
                    DetailedWorkflowModel model = e.WorkflowModel;
                    currentWorkflowId = model.Id;
                    TridentApplication.Instance.Subtitle = model.Label;
                    WorkAreaView.txtBlkWfName.Text = model.Label;
                    WorkAreaView.txtBxJbName.Text = string.Empty;
                    WorkAreaView.tabCntrPaneCtrl.SelectedIndex = 0;
                    WorkAreaView.detlsGrid.Visibility = Visibility.Visible;
                    WorkAreaView.resltsGrid.Visibility = Visibility.Visible;

                    WorkAreaView.WorkflowRequiredParameters.Clear();
                    foreach (ParameterDescriptionModel paramModel in model.WorkflowRequiredParameters)
                    {
                        WorkAreaView.WorkflowRequiredParameters.Add(paramModel);
                    }

                    WorkAreaView.WorkflowOpen = true;
                    TridentApplication.Instance.StatusMessage = string.Empty;

                    WorkAreaView.DisplayNodes.Clear();
                    WorkAreaView.DisplayNodes.Insert(0, new NodeModel(WorkflowApplicationResources.SelectNodeText));

                    foreach (NodeModel node in model.Nodes)
                    {
                        WorkAreaView.DisplayNodes.Add(node);
                    }

                    // If it having only one node select the node by default
                    if (WorkAreaView.nodesCombo.Items.Count >= 2)
                    {
                        WorkAreaView.nodesCombo.SelectedIndex = 1;
                    }
                    else if (WorkAreaView.nodesCombo.Items.Count >= 1)
                    {
                        WorkAreaView.nodesCombo.SelectedIndex = 0;
                    }
                }
            }
            catch (TridentCustomException ex)
            {
                HtmlPage.Window.Alert(ex.Message);
            }
            finally
            {
                WorkAreaView.RemoveWait();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        private static void TerminateWorkflow(object sender)
        {
            try
            {
                if (HtmlPage.Window.Confirm(WorkflowApplicationResources.TerminateWorkflowConfirmation))
                {
                    WorkAreaView.LaunchWait();
                    RegistryService.TerminateJobAsync(currentJob.JobId, TridentApplication.Instance.User);
                }
            }
            catch (Exception ex)
            {
                WorkAreaView.RemoveWait();
                HtmlPage.Window.Alert(ex.Message);
            }
        }

        #endregion

        #region Command Can Execute methods
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <returns></returns>
        private static bool WorkflowCanExecute(object sender)
        {
            return WorkAreaView.workflowOpen && !WorkAreaView.jobUnderExecution;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <returns></returns>
        private static bool WorkflowCanTerminate(object sender)
        {
            return WorkAreaView.jobUnderExecution;
        }
        #endregion

        #region Event handlers

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnLostFocus(object sender, RoutedEventArgs e)
        {
            TextBox textbox = sender as TextBox;
            ParameterDescriptionModel parameter = textbox.DataContext as ParameterDescriptionModel;
            string trimmedText = textbox.Text.Trim();
            bool validValue = ParameterDescriptionModel.ValidateInputValue(trimmedText, parameter);
            parameter.IsValueInvalid = !validValue;
            if (validValue)
            {
                parameter.Value = trimmedText;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnPreviewClick(object sender, RoutedEventArgs e)
        {
            try
            {
                WorkflowOutputModel output = ((FrameworkElement)e.OriginalSource).DataContext as WorkflowOutputModel;
                string queryString = string.Format(CultureInfo.InvariantCulture, "dpid={0}", output.Id);
                Uri targetUri = new Uri(this.application.HostUri + Constants.UriSeparator + Constants.DateProductsServicePageName + "?" + queryString, UriKind.Absolute);
                HtmlPage.Window.Navigate(targetUri, "_blank", "toolbar=0,menubar=0,resizable=0,width=450,height=350");
            }
            catch (Exception ex)
            {
                HtmlPage.Window.Alert(ex.Message);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnTick(object sender, EventArgs e)
        {
            try
            {
                this.Dispatcher.BeginInvoke(new Action(delegate()
                {
                    jobMonitorTimer.Stop();
                    if (executionCompleted)
                    {
                        FetchResults(currentJob.JobId);
                        currentJob = null;
                        ExecutionInProgress = false;
                        executionCompleted = false;
                        intervalElapsed = 0;
                    }
                    else
                    {
                        intervalElapsed++;
                        RegistryService.GetJobDetailsAsync(currentJob.JobId);
                    }
                }));
            }
            catch (Exception ex)
            {
                HtmlPage.Window.Alert(ex.Message);
            }
        }

        /// <summary>
        /// Called when [terminate job completed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Research.ScientificWorkflow.CompletedEventArgs"/> instance containing the event data.</param>
        private static void OnTerminateJobCompleted(object sender, CompletedEventArgs e)
        {
            try
            {
            if (e.Error != null)
            {
                HtmlPage.Window.Alert(e.Error.Message);
            }
}
            finally
            {
                WorkAreaView.RemoveWait();
            }

        }

        /// <summary>
        /// Called when [execute workflow completed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Research.ScientificWorkflow.ExecuteWFOnMachineCompletedEventArgs"/> instance containing the event data.</param>
        private static void OnExecuteWorkflowCompleted(object sender, ExecuteWFOnMachineCompletedEventArgs e)
        {
            try
            {
                if (e.Error != null)
                {
                    HtmlPage.Window.Alert(e.Error.Message);
                }
                else if (e.Job != null)
                {
                    currentJob = e.Job;
                    WorkflowOutputs.Clear();
                    DataProducts.Clear();

                    FetchWorkflowInputs();

                    ExecutionInProgress = true;
                    intervalElapsed = 0;

                    jobMonitorTimer.Start();
                }
            }
            finally
            {
                WorkAreaView.RemoveWait();
            }
        }


        /// <summary>
        /// Called when [get outputs completed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Research.ScientificWorkflow.GetOutputsCompletedEventArgs"/> instance containing the event data.</param>
        private static void OnGetOutputsCompleted(object sender, GetOutputsCompletedEventArgs e)
        {
            try
            {
                if (e.Error != null)
                {
                    HtmlPage.Window.Alert(e.Error.Message);
                }
                else if (e.Outputs != null)
                {
                    FetchWorkflowOutputs(e.Outputs);
                }
            }
            finally
            {
                WorkAreaView.RemoveWait();
            }
        }

        /// <summary>
        /// Called when [get job details completed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Research.ScientificWorkflow.GetJobDetailsCompletedEventArgs"/> instance containing the event data.</param>
        private static void OnGetJobDetailsCompleted(object sender, GetJobDetailsCompletedEventArgs e)
        {
            try
        {
            if (e.Error != null)
            {
                HtmlPage.Window.Alert(e.Error.Message);
            }
            else if (e.Job != null)
            {
                JobStatus status = e.Job.Status;

                if (status == JobStatus.Completed || status == JobStatus.Aborted)
                {
                    executionCompleted = true;
                    if (!string.IsNullOrEmpty(e.Job.ErrorMessage))
                    {
                        HtmlPage.Window.Alert(string.Format(CultureInfo.CurrentCulture, "{0} {1}", WorkflowApplicationResources.ExecutionFailed, e.Job.ErrorMessage));
                    }
                }
                else if (status == JobStatus.Waiting && (intervalElapsed % ConfiguredInterval) == 0)
                {
                    if (HtmlPage.Window.Confirm(WorkflowApplicationResources.JobRetryMessage))
                    {
                        TerminateWorkflow(null);
                    }
                }
                else
                {
                    FetchResults(e.Job.JobId);
                }

                TridentApplication.Instance.StatusMessage = string.Format(CultureInfo.CurrentCulture, "{0} - {1} - {2}", WorkflowApplicationResources.ExecutionStatus, e.Job.JobName, status.ToString());
                jobMonitorTimer.Start();
            }
        }
            finally
            {
                WorkAreaView.RemoveWait();
            }
        }

        #endregion

        #region Private methods

        /// <summary>
        /// 
        /// </summary>
        private void InitializeTimer()
        {
            jobMonitorTimer = new DispatcherTimer();
            jobMonitorTimer.Tick += OnTick;
            jobMonitorTimer.Interval = new TimeSpan(0, 0, 2);//TODO: Make the time span to 5 seconds
        }

        /// <summary>
        /// Determines whether the specified machine is valid.
        /// </summary>
        /// <param name="machine">The machine.</param>
        /// <returns>
        /// 	<c>true</c> if the specified machine is valid; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsValidNode(NodeModel machine)
        {
            return machine != null && !machine.NodeName.Equals(WorkflowApplicationResources.SelectNodeText, StringComparison.OrdinalIgnoreCase);
        }

        /// <summary>
        /// 
        /// </summary>
        private void AttachCommands()
        {
            JobCommands.ExecuteCommand = new DelegateCommand<object>(ExecuteWorkflow, WorkflowCanExecute);
            JobCommands.TerminateCommand = new DelegateCommand<object>(TerminateWorkflow, WorkflowCanTerminate);
            JobCommands.WorkflowSelectedCommand = new DelegateCommand<object>(WorkflowSelected);
        }

        /// <summary>
        /// 
        /// </summary>
        private static void FetchResults(Guid jobId)
        {
            RegistryService.GetOutputsAsync(jobId);
        }

        /// <summary>
        /// 
        /// </summary>
        private static void FetchWorkflowInputs()
        {
            ExecutionInputs.Clear();
            foreach (ParameterDescriptionModel paramModel in WorkflowRequiredParameters)
            {
                ExecutionInputs.Add(paramModel);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private static void RaiseCanExecuteChanged()
        {
            JobCommands.ExecuteCommand.RaiseCanExecuteChanged();
            JobCommands.TerminateCommand.RaiseCanExecuteChanged();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private string ValidateInputs()
        {
            string errorMessage = string.Empty;
            foreach (ParameterDescriptionModel parameter in WorkAreaView.WorkflowRequiredParameters)
            {
                if (!parameter.IsValueInvalid.HasValue)
                {
                    if (parameter.Value != null)
                    {
                        bool validValue = ParameterDescriptionModel.ValidateInputValue(parameter.Value.ToString(), parameter);
                        parameter.IsValueInvalid = !validValue;
                    }
                    else
                    {
                        parameter.IsValueInvalid = true;
                    }
                }
                if (parameter.IsValueInvalid.Value)
                {
                    errorMessage = WorkflowApplicationResources.EnterValidPropertyValues;
                }
            }

            if (string.IsNullOrEmpty(errorMessage) && (this.cboNode.SelectedIndex == -1 || !IsValidNode(this.cboNode.SelectedItem as NodeModel)))
            {
                errorMessage = WorkflowApplicationResources.SelectAMachine;
            }
            return errorMessage;
        }

        private static void LaunchWait()
        {
            if (WorkAreaView.LaunchWaitScreenStatic != null)
            {
                WorkAreaView.LaunchWaitScreenStatic.Invoke(TridentApplication.Instance, new EventArgs());
            }
        }

        private static void RemoveWait()
        {
            if (WorkAreaView.RemoveWaitScreenStatic != null)
            {
                WorkAreaView.RemoveWaitScreenStatic.Invoke(TridentApplication.Instance, new EventArgs());
            }
        }

        /// <summary>
        /// Fetches the workflow outputs.
        /// </summary>
        /// <param name="outputs">The outputs.</param>
        private static void FetchWorkflowOutputs(ICollection<WorkflowOutputModel> outputModels)
        {
            List<WorkflowOutputModel> outputs = outputModels as List<WorkflowOutputModel>;

            if (outputs != null)
            {
                outputs.ForEach(output =>
                {
                    if (!WorkflowOutputs.Any(w => w.Id.Equals(output.Id)))
                    {
                        if (output.IsFinal)
                        {
                            WorkflowOutputs.Add(output);
                        }
                        else
                        {
                            GetWorkflowOutputActivityModel(output);
                        }
                    }
                });
            }
        }

        /// <summary>
        /// Gets the workflow output activity model.
        /// </summary>
        /// <param name="output">The output.</param>
        /// <returns></returns>
        private static WorkflowOutputActivityModel GetWorkflowOutputActivityModel(WorkflowOutputModel output)
        {
            WorkflowOutputActivityModel requiredModel =
                DataProducts.FirstOrDefault(d => d.ActivityUniqueName.Equals(output.ActivityUniqueName, StringComparison.Ordinal));
            if (requiredModel == null)
            {
                requiredModel = new WorkflowOutputActivityModel(output.ActivityName, output.ActivityUniqueName);
                DataProducts.Add(requiredModel);
            }

            if (!requiredModel.DataProducts.Any(d => d.Id.Equals(output.Id)))
            {
                requiredModel.DataProducts.Add(output);
            }

            return requiredModel;
        }
        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when [property changed].
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises the property changed.
        /// </summary>
        /// <param name="name">The name.</param>
        protected void RaisePropertyChanged(string name)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(name));
        }

        #endregion
    }
}
